/*
 * Copyright (c) 2009, David A. Freels Sr.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that
 * the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package net.whippetcode.plugins.intellij.hudson.parser.sax;

import net.whippetcode.plugins.intellij.hudson.domain.job.Build;
import net.whippetcode.plugins.intellij.hudson.domain.job.BuildType;
import net.whippetcode.plugins.intellij.hudson.domain.job.ChangeSetItem;
import net.whippetcode.plugins.intellij.hudson.domain.job.ChangeSetItemPath;
import net.whippetcode.plugins.intellij.hudson.domain.job.Job;
import net.whippetcode.plugins.intellij.hudson.domain.job.Status;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.Serializable;

/**
 * This class is responsible for parsing the Hudson XML APi and building the
 * Job/Build/ChangeSetItem/ChangeSetItemPath hierarchy.
 *
 * @author David A. Freels Sr.
 */
public class SaxJobHandler extends DefaultHandler implements Serializable
{
	private static final long serialVersionUID = -5218174517280073325L;
	
	private static final Map<String, ITagCommand> commands = new HashMap<String, ITagCommand>();
	private static final String HUDSON = "hudson";
	private static final String LISTVIEW = "listView";
	private static final String JOB = "job";
	private static final String LAST_SUCCESSFUL_BUILD = "lastSuccessfulBuild";
	private static final String LAST_FAILED_BUILD = "lastFailedBuild";
	private static final String LAST_STABLE_BUILD = "lastStableBuild";
	private static final String ITEM = "item";

	private static final String PATH = "path";
	private StringBuilder currentText = new StringBuilder();
	private StringBuilder currentCommandName = new StringBuilder();
	private String currentTagName;
	private ITagCommand currentCommand;
	private List<Job> jobs = new ArrayList<Job>();
	private Map<Class, Object> elements = new HashMap<Class, Object>();
	private Status status = Status.DISABLED;
	private StringBuilder statusMessage;
	private String serverAddress;

	static
	{
		commands.clear();
		//Job parsers
		commands.put("/job/name", new NameTagCommand());
		commands.put("/job/color", new ColorTagCommand());
		commands.put("/job/inQueue", new QueueTagCommand());
		commands.put("/job/lastSuccessfulBuild/number", new JobRevisionTagCommand());
		commands.put("/job/lastFailedBuild/number", new JobRevisionTagCommand());
		commands.put("/job/lastStableBuild/number", new JobRevisionTagCommand());
		commands.put("/job/lastSuccessfulBuild/timestamp", new BuildDateTagCommand());
		commands.put("/job/lastFailedBuild/timestamp", new BuildDateTagCommand());
		commands.put("/job/lastStableBuild/timestamp", new BuildDateTagCommand());
		//ChangeSet parsers
		commands.put("/job/lastSuccessfulBuild/changeSet/item/msg", new MessageTagCommand());
		commands.put("/job/lastSuccessfulBuild/changeSet/item/revision", new ChangeSetItemRevisionTagCommand());
		commands.put("/job/lastSuccessfulBuild/changeSet/item/user", new ChangeSetItemUserTagCommand());
		commands.put("/job/lastFailedBuild/changeSet/item/msg", new MessageTagCommand());
		commands.put("/job/lastFailedBuild/changeSet/item/revision", new ChangeSetItemRevisionTagCommand());
		commands.put("/job/lastFailedBuild/changeSet/item/user", new ChangeSetItemUserTagCommand());
		commands.put("/job/lastStableBuild/changeSet/item/msg", new MessageTagCommand());
		commands.put("/job/lastStableBuild/changeSet/item/revision", new ChangeSetItemRevisionTagCommand());
		commands.put("/job/lastStableBuild/changeSet/item/user", new ChangeSetItemUserTagCommand());
		//ChangeSetItemPath parsers
		commands.put("/job/lastSuccessfulBuild/changeSet/item/path/editType", new ChangeSetItemPathEditTypeTagCommand());
		commands.put("/job/lastSuccessfulBuild/changeSet/item/path/file", new ChangeSetItemPathFileTagCommand());
		commands.put("/job/lastFailedBuild/changeSet/item/path/editType", new ChangeSetItemPathEditTypeTagCommand());
		commands.put("/job/lastFailedBuild/changeSet/item/path/file", new ChangeSetItemPathFileTagCommand());
		commands.put("/job/lastStableBuild/changeSet/item/path/editType", new ChangeSetItemPathEditTypeTagCommand());
		commands.put("/job/lastStableBuild/changeSet/item/path/file", new ChangeSetItemPathFileTagCommand());
	}

	public SaxJobHandler(String serverAddress)
	{
		this.serverAddress = serverAddress;
		statusMessage = new StringBuilder("<html><body><table border=0>");
	}

	public List<Job> getJobs()
	{
		return jobs;
	}

	public Status getStatus()
	{
		return status;
	}

	public String getStatusSummary()
	{
		return statusMessage.toString();
	}

	@Override
	public void endDocument() throws SAXException
	{
		statusMessage.append("</table></body></html>");

		//Make sure any empty elements are destroyed.
		JobPruner.pruneEmptyElements(jobs);
	}

	@Override
	public void characters(char[] chars, int start, int length) throws SAXException
	{
		if (currentCommand != null)
		{
			currentText.append(chars, start, length);
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
	{
		if (HUDSON.equals(qName) ||
			 LISTVIEW.equals(qName))
		{
			return;
		}

		currentTagName = qName;
		if (JOB.equals(qName) && currentCommandName.length() == 0)
		{
			Job job = new Job();
			job.setServerAddress(this.serverAddress);
			elements.put(Job.class, job);
			jobs.add(job);
		}
		if (currentCommandName.indexOf("/job") == 0)
		{
			if ((LAST_SUCCESSFUL_BUILD.equals(qName) ||
				 LAST_STABLE_BUILD.equals(qName) ||
				 LAST_FAILED_BUILD.equals(qName)))
			{
				Build build = new Build();
				build.setType(getBuildType(qName));
				build.setServerName(this.serverAddress);
				elements.put(Build.class, build);
				Job job = (Job) elements.get(Job.class);
				job.addChild(build);
				build.setName(job.getName());
			}
			else if (ITEM.equals(qName) && elements.get(Build.class) != null)
			{
				ChangeSetItem item = new ChangeSetItem();
				((Build) elements.get(Build.class)).addItem(item);
				elements.put(ChangeSetItem.class, item);
			}
			else if (PATH.equals(qName) && elements.get(ChangeSetItem.class) != null)
			{
				ChangeSetItemPath path = new ChangeSetItemPath();
				((ChangeSetItem) elements.get(ChangeSetItem.class)).addPath(path);
				elements.put(ChangeSetItemPath.class, path);
			}
		}

		if (!commands.containsKey(qName) &&
			 !commands.containsKey(currentCommandName.toString()))
		{
			currentCommandName.append("/").append(qName);
		}

		if (commands.containsKey(currentCommandName.toString()))
		{
			currentCommand = commands.get(currentCommandName.toString());
			currentText = new StringBuilder();
		}
		else
		{
			currentCommand = null;
		}
	}

	private BuildType getBuildType(String tagName)
	{
		if (LAST_FAILED_BUILD.equals(tagName))
		{
			return BuildType.FAILED;
		}
		else if (LAST_STABLE_BUILD.equals(tagName))
		{
			return BuildType.STABLE;
		}
		else if (LAST_SUCCESSFUL_BUILD.equals(tagName))
		{
			return BuildType.SUCCESSFUL;
		}

		return BuildType.UNDEFINED;
	}

	@SuppressWarnings({"unchecked"})
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException
	{
		if ((currentCommandName.indexOf("/job") == 0 &&
			 currentCommandName.indexOf("/job/") == -1) &&
			 JOB.equals(qName))
		{
			parseStatus((Job) elements.get(Job.class));
		}
		if (qName.equals(currentTagName) && currentCommand != null &&
			 elements.get(currentCommand.getSupportedClass()) != null)
		{
			currentCommand.processText(currentText.toString(), elements.get(currentCommand.getSupportedClass()));
			currentText = new StringBuilder();
			currentCommand = null;
		}

		if (!commands.containsKey(qName) && currentCommandName.indexOf("/" + qName) != -1)
		{
			int index = currentCommandName.indexOf("/" + qName);
			currentCommandName.delete(index, (index + qName.length() + 1));
		}
	}

	private void parseStatus(Job job)
	{
		if (job == null)
		{
			return;
		}

		if (status.compareTo(job.getStatus()) > 0)
		{
			status = job.getStatus();
		}

		statusMessage.append("<tr><td>").append(job.getName()).append("</td><td bgcolor=").append(job.getStatus() == Status.DISABLED ? "gray" : job.getStatus().name().toLowerCase()).append("></td></tr>");
	}
}